﻿using OpenNETCF.IoC;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Xml.Linq;

namespace IoTCM
{
    class HttpServer : IDisposable
    {
        private readonly HttpListener m_listener;
        private readonly Thread m_listenerThread;
        private readonly Thread[] m_workers;
        private readonly ManualResetEvent m_stop, m_ready;
        private Queue<HttpListenerContext> m_queue;

        private IThingProvider m_provider;
        private RequestProcessor m_processor;
        
        public bool IsRunning { get; private set; }

        public HttpServer()
            : this(10)
        {
        }

        public HttpServer(int maxThreads)
        {
            // create the Thing Provider
            // TODO: inject this
            m_provider = new SampleThingProvider();

            IsRunning = false;

            // TODO: pull from config:
            var defaultFormat = "xml";
            var sessionsEnabled = false;
            m_processor = new RequestProcessor(m_provider, sessionsEnabled);


            // create the web server objects
            m_workers = new Thread[maxThreads];
            m_queue = new Queue<HttpListenerContext>();
            m_stop = new ManualResetEvent(false);
            m_ready = new ManualResetEvent(false);
            m_listener = new HttpListener();
            m_listenerThread = new Thread(HandleRequests);
        }

        public void Start(int port)
        {
            Console.WriteLine(string.Format("==== IoTCM Reference Server Started at [insert IP here]:{0} ====", port));

            Console.WriteLine("\tFormats Supported:");
            foreach (var format in m_processor.SupportedFormats)
            {
                Console.WriteLine("\t\t" + format);
            }

            m_listener.Prefixes.Add(String.Format(@"http://+:{0}/", port));
            m_listener.Start();
            m_listenerThread.Start();

            for (int i = 0; i < m_workers.Length; i++)
            {
                m_workers[i] = new Thread(Worker);
                m_workers[i].Start();
            }

            IsRunning = true;
        }

        public void Dispose()
        { 
            Stop(); 
        }

        public void Stop()
        {
            m_stop.Set();
            m_listenerThread.Join();
            foreach (Thread worker in m_workers)
            {
                worker.Join();
            }
            m_listener.Stop();

            IsRunning = false;
        }

        private void HandleRequests()
        {
            while (m_listener.IsListening)
            {
                var context = m_listener.BeginGetContext(ContextReady, null);

                if (0 == WaitHandle.WaitAny(new[] { m_stop, context.AsyncWaitHandle }))
                {
                    return;
                }
            }
        }

        private void ContextReady(IAsyncResult ar)
        {
            try
            {
                lock (m_queue)
                {
                    m_queue.Enqueue(m_listener.EndGetContext(ar));
                    m_ready.Set();
                }
            }
            catch { return; }
        }

        private void Worker()
        {
            WaitHandle[] wait = new[] { m_ready, m_stop };
            while (0 == WaitHandle.WaitAny(wait))
            {
                HttpListenerContext context;
                lock (m_queue)
                {
                    if (m_queue.Count > 0)
                    {
                        context = m_queue.Dequeue();
                    }
                    else
                    {
                        m_ready.Reset();
                        continue;
                    }
                }

                try 
                { 
                    m_processor.ProcessRequest(context); 
                }
                catch (Exception e) 
                { 
                    Console.Error.WriteLine(e); 
                }
            }
        }
    }
}
